package com.frank.subject.infra.es.subject.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.frank.config.es.config.entity.EsIndexInfo;
import com.frank.config.es.config.entity.EsRestClient;
import com.frank.config.es.config.entity.EsSearchRequest;
import com.frank.config.es.config.entity.EsSourceData;
import com.frank.common.entity.PageInfo;
import com.frank.subject.infra.es.subject.convert.SubjectInfoEsConvert;
import com.frank.subject.infra.es.subject.entity.SubjectInfoEs;
import com.frank.subject.infra.es.subject.fields.SubjectInfoFields;
import com.frank.subject.infra.es.subject.service.SubjectInfoEsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Frank Zou
 * @version v0.0.1
 * @date 2024/4/21
 * @description
 */
@Slf4j
@Service
public class SubjectInfoEsServiceImpl implements SubjectInfoEsService {


    @Value("${elasticsearch.client.default_cluster_name}")
    private String clusterName;

    @Value("${elasticsearch.client.index_name.subject_index}")
    private String indexName;

    @Override
    public void insertSubject(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        esSourceData.setDocId(subjectInfoEs.getId() + "");
        esSourceData.setData(SubjectInfoEsConvert.convertSubjectInfoEsToMap(subjectInfoEs));
        EsRestClient.insertDoc(getEsIndexInfo(), esSourceData);
    }

    @Override
    public void insertBatchSubject(List<SubjectInfoEs> subjectInfoEsList) {
        if (!CollectionUtils.isEmpty(subjectInfoEsList)) {
            List<EsSourceData> collect = subjectInfoEsList.stream().map(subjectInfoEs -> {
                EsSourceData esSourceData = new EsSourceData();
                esSourceData.setDocId(subjectInfoEs.getId() + "");
                esSourceData.setData(SubjectInfoEsConvert.convertSubjectInfoEsToMap(subjectInfoEs));
                return esSourceData;
            }).collect(Collectors.toList());
            EsRestClient.insertBatchDoc(getEsIndexInfo(), collect);
        }
    }

    @Override
    public void updateSubject(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        esSourceData.setDocId(subjectInfoEs.getId() + "");
        Map<String, Object> data = SubjectInfoEsConvert.convertSubjectInfoEsToMap(subjectInfoEs);
        esSourceData.setData(data);
        EsRestClient.updateDoc(getEsIndexInfo(), esSourceData);
    }

    @Override
    public void updateBatchSubject(List<SubjectInfoEs> subjectInfoEsList) {
        if (!CollectionUtils.isEmpty(subjectInfoEsList)) {
            List<EsSourceData> collect = subjectInfoEsList.stream().map(subjectInfoEs -> {
                EsSourceData esSourceData = new EsSourceData();
                esSourceData.setDocId(subjectInfoEs.getId() + "");
                esSourceData.setData(SubjectInfoEsConvert.convertSubjectInfoEsToMap(subjectInfoEs));
                return esSourceData;
            }).collect(Collectors.toList());
            EsRestClient.updateBatchDoc(getEsIndexInfo(), collect);
        }
    }

    @Override
    public void deleteSubject(String subjectId) {
        EsRestClient.deleteDoc(getEsIndexInfo(), subjectId);
    }

    @Override
    public Page<SubjectInfoEs> page(SubjectInfoEs subjectInfoEs, PageInfo pageInfo) {
        EsSearchRequest searchRequest = createSearchRequestList(subjectInfoEs, pageInfo);
        SearchResponse searchResponse = EsRestClient.searchWithTermQuery(getEsIndexInfo(), searchRequest);
        List<SubjectInfoEs> records = new LinkedList<>();
        SearchHits searchHits = searchResponse.getHits();
        if (Objects.isNull(searchHits) || Objects.isNull(searchHits.getHits())) {
            Page<SubjectInfoEs> page = new Page<>();
            page.setTotal(0);
            page.setPages(0);
            page.setRecords(records);
            page.setCurrent(pageInfo.getCurrent());
            page.setSize(pageInfo.getPageSize());
            return page;
        }
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            SubjectInfoEs record = SubjectInfoEsConvert.convertSearchResponseToSubjectInfoEs(hit);
            if (Objects.nonNull(record)) {
                records.add(record);
            }
        }
        Page<SubjectInfoEs> page = new Page<>();
        assert searchHits.getTotalHits() != null;
        page.setTotal(searchHits.getTotalHits().value);
        page.setPages((int) Math.ceil(searchHits.getTotalHits().value / (double) pageInfo.getPageSize()));
        page.setRecords(records);
        page.setCurrent(pageInfo.getCurrent());
        page.setSize(pageInfo.getPageSize());
        return page;
    }

    @Override
    public SubjectInfoEs get(String subjectId) {
        Map<String, Object> doc = EsRestClient.getDocById(getEsIndexInfo(), subjectId);
        if (CollectionUtils.isEmpty(doc)) {
            return null;
        }
        return SubjectInfoEsConvert.convertSourceMapToSubjectInfoEs(doc);
    }


    private EsSearchRequest createSearchRequestList(SubjectInfoEs subjectInfoEs, PageInfo pageInfo) {
        EsSearchRequest searchRequest = new EsSearchRequest();
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        boolean isHighlight = false;
        if (StringUtils.isNotBlank(pageInfo.getSearch())) {
            // Search 不为空，开始模糊查询

            MatchQueryBuilder nameQuery = QueryBuilders.matchQuery(SubjectInfoFields.SUBJECT_NAME, pageInfo.getSearch())

                    // 自动模糊匹配，用于处理拼写错误或相近词
                    .fuzziness(Fuzziness.ONE)
                    // 设置匹配前缀长度为 0，表示从词的开头开始匹配
                    .prefixLength(1)
                    .maxExpansions(100)
                    // 提升这个查询的权重，使其在最终结果中的排序更高
                    .boost(2.0f);
            // 将标题查询放到 QueryBuilder 中， 可选命中
            queryBuilder.should(nameQuery);
            // 查询答案
            MatchQueryBuilder companyQuery = QueryBuilders.matchQuery(SubjectInfoFields. SUBJECT_ANSWER, pageInfo.getSearch())
                    .fuzziness(Fuzziness.ONE).prefixLength(0).boost(1.5f);
            queryBuilder.should(companyQuery);


            // 全文检索至少要命中一个
            queryBuilder.minimumShouldMatch(1);


            // 处于搜索状态，开启高亮
            isHighlight = true;
        }

        if (subjectInfoEs.getSubjectScore() != null){
            queryBuilder.must(QueryBuilders.termQuery(SubjectInfoFields.SUBJECT_SCORE, subjectInfoEs.getSubjectScore()));
        }
//
        if (subjectInfoEs.getSubjectType() != null){
            queryBuilder.must(QueryBuilders.termQuery(SubjectInfoFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType()));
        }

        if (subjectInfoEs.getCategoryId() != null){
            queryBuilder.must(QueryBuilders.termsQuery(SubjectInfoFields.CATEGORY_ID_LIST, Collections.singletonList(subjectInfoEs.getCategoryId())));
        }
//
        if (subjectInfoEs.getLabelId() != null){
            queryBuilder.must(QueryBuilders.termsQuery(SubjectInfoFields.LABEL_ID_LIST, Collections.singletonList(subjectInfoEs.getLabelId())));
        }

        searchRequest.setQueryBuilder(queryBuilder);

        if (isHighlight) {
            // 设置高亮字段
            HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
            highlightBuilder.preTags("<span style='color:red'>").postTags("</span>");
            searchRequest.setHighlightBuilder(highlightBuilder);
        }

        // 设置返回字段
        searchRequest.setFields(SubjectInfoFields.QUERY_FIELDS);

        searchRequest.setFrom((pageInfo.getCurrent() - 1) * pageInfo.getPageSize());
        searchRequest.setSize(pageInfo.getPageSize());

        // 不需要快照缓存
        searchRequest.setNeedScroll(false);

        // 设置排序
        List<FieldSortBuilder> sortBuilders = new ArrayList<>();
        sortBuilders.add(new FieldSortBuilder(SubjectInfoFields.CREATED_TIME).order(SortOrder.DESC));

        searchRequest.setFieldSortBuilders(sortBuilders);
        return searchRequest;

    }

    private EsIndexInfo getEsIndexInfo() {
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        esIndexInfo.setIndexName(indexName);
        esIndexInfo.setClusterName(clusterName);
        return esIndexInfo;
    }
}
